using System;
using System.Collections.Generic;
using System.Text;

using csammisrun.utility;
using csammisrun.MsnLib.Utility;

namespace csammisrun.MsnLib
{
  /*
   * This file contains all of the types of messages that MsnLib supports sending and/or receiving
   */

  /// <summary>
  /// Defines an MSN message base class
  /// </summary>
  public abstract class MsnMessage
  {
    #region Public static methods
    /// <summary>
    /// Returns a correctly typed MsnMessage descendent class from an MsnCommand
    /// </summary>
    public static MsnMessage CreateMessageFromCommand(MsnCommand command)
    {
      switch (command.Parameters[1])
      {
        case "A":
          return new TextMessage(command);
        case "N":
          return new NudgeMessage(command);
        case "U":
          return new ControlMessage(command);
      }
      return null;
    }
    #endregion

    private string _remoteuser = "";
    private string _contenttype = "";
    private string _charset = "";
    private string _body = "";
    private char _flag = '\0';
    private MsnCommand _trigger = null;
    private Dictionary<string, string> _attributes = new Dictionary<string, string>();

    /// <summary>
    /// Initializes an empty outgoing message
    /// </summary>
    public MsnMessage(string destination)
    {
      _remoteuser = destination;
    }

    /// <summary>
    /// Initializes the basic components of a message from a received command
    /// </summary>
    public MsnMessage(MsnCommand command)
    {
      string bodystr = "";
      string[] attributes = null;
      string[] tokens = null;
      string[] elementsplitter = new string[] { ": " };
      int attribute_end = 0, index_semi = 0, index_equals = 0;

      _trigger = command;
      _remoteuser = command.Parameters[0];
      bodystr = Encoding.UTF8.GetString(command.Payload);
      attribute_end = bodystr.IndexOf("\r\n");

      attributes = bodystr.Substring(0, attribute_end).Split(new string[] { "\r\n" },
        StringSplitOptions.None);

      foreach (string attribute in attributes)
      {
        tokens = attribute.Split(elementsplitter, StringSplitOptions.None);

        switch (tokens[0])
        {
          case "MIME-Version":
            break;

          case "Content-Type":
            // See if the charset is defined in there, grab it if it is
            if ((index_semi = tokens[1].IndexOf(';')) != -1)
            {
              if ((index_equals = tokens[1].IndexOf('=', index_semi)) != -1)
              {
                index_equals++;
                Charset = tokens[1].Substring(index_equals);
              }
              ContentType = tokens[1].Substring(0, index_semi);
            }
            else
            {
              ContentType = tokens[1];
            }
            break;

          default:
            _attributes[tokens[0]] = tokens[1];
            break;
        }
      }

      // Take the rest of the payload and store it as the message body
      _body = bodystr.Substring(attribute_end + 4);
    }

    #region Methods
    /// <summary>
    /// Sets a message attribute
    /// </summary>
    public void SetAttribute(string attribute, string value)
    {
      _attributes[attribute] = value;
    }

    public abstract void ProcessSwitchboardMessage(Switchboard sb);
    public abstract void ProcessNotificationMessage(Notification nb);

    /// <summary>
    /// Gets a message attribute
    /// </summary>
    /// <returns>The value of the attribute, or "" if it does not exist</returns>
    public string GetAttribute(string attribute)
    {
      if (_attributes.ContainsKey(attribute))
      {
        return _attributes[attribute];
      }
      return "";
    }

    /// <summary>
    /// Returns a string with the MIME-Version, the message attributes, and the body in UTF-8 encoding
    /// </summary>
    public override string ToString()
    {
      StringBuilder retval = new StringBuilder();
      if (String.IsNullOrEmpty(_charset))
      {
        retval.AppendFormat("MIME-Version: 1.0\r\nContent-Type: {0}\r\n", _contenttype);
      }
      else
      {
        retval.AppendFormat("MIME-Version: 1.0\r\nContent-Type: {0}; charset={1}\r\n", _contenttype, _charset);
      }

      foreach (KeyValuePair<string, string> pair in _attributes)
      {
        retval.AppendFormat("{0}: {1}\r\n", pair.Key, pair.Value);
      }

      retval.AppendFormat("\r\n{0}", _body);
      return retval.ToString();
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets the UTF-8 encoded contents of the message
    /// </summary>
    protected string Body
    {
      get { return _body; }
      set { _body = value; }
    }

    /// <summary>
    /// Gets or sets the Passport of the remote user
    /// </summary>
    public string RemoteUser
    {
      get { return _remoteuser; }
      set { _remoteuser = value; }
    }

    /// <summary>
    /// Gets the Content-Type of the message
    /// </summary>
    public string ContentType
    {
      get { return _contenttype; }
      protected internal set { _contenttype = value; }
    }

    /// <summary>
    /// Gets the character set of the message
    /// </summary>
    public string Charset
    {
      get { return _charset; }
      internal set { _charset = value; }
    }

    /// <summary>
    /// Gets or sets the flag for the message
    /// </summary>
    protected char Flag
    {
      get { return _flag; }
      set { _flag = value; }
    }

    /// <summary>
    /// Gets the <see cref="MsnCommand"/> that created this message
    /// </summary>
    protected MsnCommand Trigger
    {
      get { return _trigger; }
    }
    #endregion
  }

  /// <summary>
  /// Defines an MSN text message
  /// </summary>
  public class TextMessage : MsnMessage
  {
    private string _html = "";

    /// <summary>
    /// Initializes a TextMessage from a received command
    /// </summary>
    public TextMessage(MsnCommand command)
      : base(command)
    {
      _html = GenerateHTML();
    }

    /// <summary>
    /// Initializes an outgoing TextMessage
    /// </summary>
    public TextMessage(string destination)
      : base(destination)
    {
      Flag = 'A';
      Charset = "UTF-8";
      SetAttribute("User-Agent", "MsnLib/" + Session.MSNLIB_VERSION);
      ContentType = "text/plain";
    }

    #region Methods
    public override void ProcessSwitchboardMessage(Switchboard sb)
    {
      User remote = sb.GetSwitchboardUser(RemoteUser);
      if (remote != null)
      {
        if (sb.SubType == SwitchboardSubType.InstantMessage)
        {
          sb.ParentSession.OnMessageReceived(remote, HTML);
        }
        else
        {
          ChatRoom target = sb.ParentSession.FindChatroomBySwitchboard(sb);
          sb.ParentSession.OnChatRoomMessageReceived(target, remote, HTML);
        }
      }
      else
      {
        Logging.WriteString("Received a message for a user that isn't here: " + RemoteUser);
      }
    }

    public override void ProcessNotificationMessage(Notification nb)
    {
      // No message handling for notification boards
    }

    private string GenerateHTML()
    {
      // If the message doesn't have a body, return an empty document
      if (String.IsNullOrEmpty(Body))
      {
        return "<html></html>";
      }

      // Get the HTML-encoded body string
      string body = HttpUtility.HtmlEncode(Body);

      if (GetAttribute("X-MMS-IM-Format") == "")
      {
        return "<html>" + body + "</html>";
      }

      StringBuilder prefix = new StringBuilder();
      StringBuilder postfix = new StringBuilder();
      string format = "";
      int attribindex = 0, endindex = 0, tmp = 0;
      List<int> colors = new List<int>();

      format = GetAttribute("X-MMS-IM-Format");
      // Check for the font face attribute
      attribindex = format.IndexOf("FN=");
      if (attribindex != -1 && format[attribindex = attribindex + 3] != ';')
      {
        endindex = format.IndexOf(';', attribindex);
        prefix.AppendFormat("<font face=\"{0}\">", format.Substring(attribindex, endindex - attribindex));
        postfix.Insert(0, "</font>");
      }
      // Check for font attributes
      attribindex = format.IndexOf("EF=");
      if (attribindex != -1 && format[attribindex = attribindex + 3] != ';')
      {
        endindex = format.IndexOf(';', attribindex);
        foreach (char c in format.Substring(attribindex, endindex - attribindex).ToLower())
        {
          prefix.AppendFormat("<{0}>", c);
          postfix.Insert(0, String.Format("</{0}>", c));
        }
      }
      // Check for colors
      attribindex = format.IndexOf("CO=");
      if (attribindex != -1 && format[attribindex = attribindex + 3] != ';')
      {
        endindex = format.IndexOf(';', attribindex) - 1;
        do
        {
          if (attribindex + 2 < endindex)
          {
            colors.Add(Int32.Parse(format.Substring(attribindex, 2), System.Globalization.NumberStyles.HexNumber));
          }
          else
          {
            colors.Add(0);
          }
          attribindex += 2;
        } while (attribindex < endindex);

        if (colors.Count != 0)
        {
          if (colors.Count == 1)
          {
            colors.Add(0);
            colors.Add(0);
          }
          else if (colors.Count == 2)
          {
            tmp = colors[0];
            colors[0] = colors[1];
            colors[1] = tmp;
            colors.Add(0);
          }
          else if (colors.Count == 3)
          {
            tmp = colors[0];
            colors[0] = colors[2];
            colors[2] = tmp;
          }

          prefix.AppendFormat("<font color=\"#{0:x2}{1:x2}{2:x2}\">", colors[0], colors[1], colors[2]);
          postfix.Insert(0, "</font>");
        }
      }

      return HttpUtility.UrlDecode(prefix.ToString()) + body + HttpUtility.UrlDecode(postfix.ToString());
    }

    public void TranslateHTML()
    {
      if (_html.ToLower().StartsWith("<html>"))
      {
        _html = _html.Substring(6);
        _html = _html.Substring(0, _html.Length - 7);
      }

      int tagendindex = 0;
      int tagnamelength = 0;
      int closetaglength = 0;
      string tagcontents = "";

      // Pull off starting HTML tags
      List<string> formatting = new List<string>();
      while (_html[0] == '<')
      {
        tagendindex = _html.IndexOf('>');
        tagcontents = _html.Substring(1, tagendindex - 1);
        tagnamelength = _html.IndexOfAny(new char[] { ' ', '>' }) - 1;
        closetaglength = tagnamelength + 3; // < / tagname >
        _html = _html.Substring(tagendindex + 1, _html.Length - tagendindex - closetaglength - 1);
        formatting.Add(tagcontents);
      }

      // Clip out all HTML tags in the middle of the message, MSN doesn't support inline HTML
      string bodytext = System.Text.RegularExpressions.Regex.Replace(_html, "<.*?>", "");

      // Build the X-MMS-IM-Format attribute
      string format = "";
      string texteffects = "";
      foreach (string frmt in formatting)
      {
        string tag = frmt.ToLower();
        if (tag == "b")
        {
          texteffects += "B";
        }
        else if (tag == "i")
        {
          texteffects += "I";
        }
        else if (tag == "u")
        {
          texteffects += "U";
        }
        else if (tag == "s")
        {
          texteffects += "S";
        }
        else if (tag.StartsWith("font"))
        {
          string[] attributes = tag.Split(' ');
          foreach (string attribute in attributes)
          {
            string[] kvp = attribute.Split('=');
            if (kvp.Length != 2)
            {
              continue;
            }

            kvp[1] = kvp[1].Trim('"');
            switch (kvp[0])
            {
              case "face":
                format += "FN=" + kvp[1] + ";";
                break;
              case "color":
                format += "CO=" + kvp[1].Trim('#') + ";";
                break;
            }
          }
        }
      }

      if (texteffects != "")
      {
        format += "EF=" + texteffects + ";";
      }

      // Fill out the message and return
      if (format != "")
      {
        SetAttribute("X-MMS-IM-Format", format);
      }

      Body = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(bodytext));
      // TODO:  Make sure all the rest of the attributes are filled out
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets the HTML content of this message
    /// </summary>
    public string HTML
    {
      get { return _html; }
      set
      {
        _html = value;
        TranslateHTML();
      }
    }
    #endregion
  }

  /// <summary>
  /// Defines an MSN Nudge message
  /// </summary>
  public class NudgeMessage : MsnMessage
  {
    /// <summary>
    /// Initializes an incoming NudgeMessage
    /// </summary>
    /// <param name="command"></param>
    public NudgeMessage(MsnCommand command)
      : base(command)
    {
    }

    /// <summary>
    /// Initializes an outgoing NudgeMessage
    /// </summary>
    public NudgeMessage(string destination)
      : base(destination)
    {
      SetAttribute("ID", "1\r\n");
      Flag = 'N';
      ContentType = "text/x-msnmsgr-datacast\r\n";
    }

    #region Methods
    public override void ProcessSwitchboardMessage(Switchboard sb)
    {
      User remote = sb.GetSwitchboardUser(RemoteUser);
      if (remote != null)
      {
        sb.ParentSession.OnNudgeReceived(remote);
      }
      else
      {
        Logging.WriteString("Received a nudge from a user that isn't here: " + RemoteUser);
      }
    }

    public override void ProcessNotificationMessage(Notification nb)
    {
      // No message handling for notification boards
    }
    #endregion
  }

  /// <summary>
  /// Defines an MSN control message
  /// </summary>
  public class ControlMessage : MsnMessage
  {
    /// <summary>
    /// Initializes an incoming ControlMessage
    /// </summary>
    public ControlMessage(MsnCommand command)
      : base(command)
    {
    }

    /// <summary>
    /// Initializes an outgoing ControlMessage
    /// </summary>
    public ControlMessage(string destination)
      : base(destination)
    {
      Flag = 'U';
      ContentType = "text/x-msmsgscontrol";
      Body = "\r\n";
    }

    #region Methods
    public override void ProcessSwitchboardMessage(Switchboard sb)
    {
      User remote = sb.GetSwitchboardUser(RemoteUser);
      if (remote != null)
      {
        if (!String.IsNullOrEmpty(GetAttribute("TypingUser")))
        {
          if (sb.SubType == SwitchboardSubType.InstantMessage)
          {
            sb.ParentSession.OnTypingNotificationReceived(remote);
          }
          else
          {
            ChatRoom target = sb.ParentSession.FindChatroomBySwitchboard(sb);
            sb.ParentSession.OnChatRoomTypingNotificationReceived(target, remote);
          }
        }
      }
      else
      {
        Logging.WriteString("Received a control message from a user that isn't here: " + RemoteUser);
      }
    }

    public override void ProcessNotificationMessage(Notification nb)
    {
      // No message handling for notification boards
    }
    #endregion
  }

  /// <summary>
  /// Defines a message describing a portion of the user's profile
  /// </summary>
  public class ProfileMessage : MsnMessage
  {
    /// <summary>
    /// Initializes an incoming profile message
    /// </summary>
    public ProfileMessage(MsnCommand command) 
      : base(command)
    {
    }

    #region Methods
    public override void ProcessSwitchboardMessage(Switchboard sb)
    {
      // Doesn't get received on switchboards
    }

    public override void ProcessNotificationMessage(Notification nb)
    {
      Session parent = null; // nb.parentsession
      int intval = 0;
      long longval = 0;

      parent.PassportInfo.Kv = GetAttribute("kv");
      parent.PassportInfo.Sid = GetAttribute("sid");
      parent.PassportInfo.Auth = GetAttribute("MSPAuth");
      parent.PassportInfo.ClientIP = GetAttribute("ClientIP");
      int.TryParse(GetAttribute("ClientPort"), out intval);
      long.TryParse(GetAttribute("LoginTime"), out longval);
      parent.PassportInfo.ClientPort = intval;
      parent.PassportInfo.Sl = longval;
    }
    #endregion
  }

  //public class InitialEmailMessage : MsnMessage
  //{
  //  public override void ProcessSwitchboardMessage(Switchboard sb)
  //  {
  //    // Not received on switchboards
  //  }

  //  public override void ProcessNotificationMessage(Notification nb)
  //  {
  //    if (RemoteUser != "Hotmail")
  //    {
  //      return;
  //    }

  //    if (String.IsNullOrEmpty(nb.ParentSession.PassportInfo.Redirecter))
  //    {
  //      Transaction trans = new Transaction(nb.Processor, "URL", "{0}", "INBOX");
  //      trans.SetTriggeringCommand(this.Trigger);
  //      trans.Send();
  //      return;
  //    }
  //  }
  //}
}
